package mediaCoreLib.managers
{
	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.events.IOErrorEvent;
	import flash.media.Sound;
	import flash.media.SoundChannel;
	import flash.media.SoundMixer;
	import flash.net.URLRequest;
	
	import mediaCoreLib.controls.IMediaControl;
	
	/**
	 * Triggered when the index has changed.
	 * Used to notify views that the currently playing media has changed.
	 */
	[Event(name="indexChanged", type="flash.events.Event")]
	
	/**
	 * Singleton class that provides interface to play and stop playlists of media.
	 */
	public class PlaylistManager extends EventDispatcher implements IPlaylistManager
	{
		//////////////////////////////////////////////////////////
		//	GET INSTANCE (singleton)
		//////////////////////////////////////////////////////////
		
		static private var _instance:PlaylistManager;
		
		static public function getInstance ():PlaylistManager
		{
			if (!_instance)
				_instance = new PlaylistManager();
				
			return _instance;
		}
		
		//////////////////////////////////////////////////////////
		//	CONSTRUCTOR (singleton for mxml)
		//////////////////////////////////////////////////////////
		
		public function PlaylistManager ()
		{
			super();
			
			if (_instance)
			{
				throw new Error("PlaylistManager is a Singleton-type class.  Only one instance can be instantiated");
				return;
			}
			
			_instance = this;
		}
		
		//////////////////////////////////////////////////////////
		//	INDEX
		//////////////////////////////////////////////////////////
		
		protected var currentIndex:int = -1;
		
		[Bindable("indexChanged")]
		public function get index ():int
		{
			return currentIndex;
		}
		
		public function set index (value:int):void
		{			
			if (currentIndex != value)
			{
				currentIndex = value;
				
				dispatchEvent(new Event("indexChanged"));
			}
		}
		
		//////////////////////////////////////////////////////////
		//	PLAYLIST
		//////////////////////////////////////////////////////////
		
		protected var playlistCollection:Array = [];
		
		[Bindable("playlistChanged")]
		public function get playlist ():Array
		{
			return playlistCollection; 
		}
		
		public function set playlist (value:Array):void
		{
			if (playlistCollection.toString() != value.toString())
			{
				playlistCollection = value;
				downloadPlaylistMedia(); //we need to go get the data again
				
				dispatchEvent(new Event("playlistChanged"));
			}
		}
		
		//////////////////////////////////////////////////////////
		//	IS PLAYING
		//////////////////////////////////////////////////////////
		
		protected var playing:Boolean = false;
		
		[Bindable("isPlayingChanged")]
		public function get isPlaying ():Boolean
		{
			return playing;
		}
		
		//////////////////////////////////////////////////////////
		//	PLAYLIST CONTROLS
		//////////////////////////////////////////////////////////
		
		protected var currentMediaControl:IMediaControl;
		
		protected var currentChannel:SoundChannel;
		protected var currentSound:Sound;	
				
		protected var isFirstSound:Boolean = false;
		
		[ArrayElementType("String")]
		protected var playlistURLs:Array = []; // simple list of urls to fetch media
		
		[ArrayElementType("flash.media.Sound")]
		protected var queue:Array = []; // list of sound files
 		
		//////////////////////////////////////////////////////////
		//	IPLAYLIST
		//////////////////////////////////////////////////////////
		
		protected var currentPosition:Number;
		
		public function pausePlaylist ():void
		{
			if (playing)
			{				
				currentPosition = currentChannel.position;
				
				currentChannel.stop();
				currentChannel.removeEventListener(Event.SOUND_COMPLETE, onSoundComplete_channelHandler, false);
			}
			
			else
			{
				currentChannel = currentSound.play(currentPosition);
				currentChannel.addEventListener(Event.SOUND_COMPLETE, onSoundComplete_channelHandler, false, 0.0, false);
			}
			
			playing = !playing;
			
			if (currentMediaControl)
					currentMediaControl.isPlaying = false;
			
			dispatchEvent(new Event("isPlayingChanged"));
		}
		
		public function startPlaylist (playlist:Array, mediaControl:IMediaControl = null):void
		{
			SoundMixer.stopAll();
			
			if (playlistURLs.toString() != playlist.toString())
			{
				playlistURLs = playlist;
				queue = [];
				
				index = startIndex;
				
				playlistCollection = [];
				var url:String;
				for each (url in playlist)
					playlistCollection.push(url);
				
				dispatchEvent(new Event("playlistChanged")); //trigger bindings where applicable
			}
			
			downloadPlaylistMedia(); // at this point, playing of media is delegated to the handlers below
			
			if (currentMediaControl && currentMediaControl != mediaControl) //stop visual indication that a previous track might be playing.
				currentMediaControl.isPlaying = false; //shut off old media control visual indicator
			
			currentMediaControl = mediaControl;
			
			isFirstSound = true;
			playing = true;
			
			dispatchEvent(new Event("isPlayingChanged"));
		}
		
		public function stopPlaylist ():void
		{
			playing = false;
			if (currentMediaControl)
				currentMediaControl.isPlaying = false;
			
			if (currentChannel)
				currentChannel.stop();
			
			SoundMixer.stopAll();
		}
		
		//////////////////////////////////////////////////////////
		//	
		//////////////////////////////////////////////////////////
		
		protected function downloadPlaylistMedia ():void
		{
			var request:URLRequest;
			var sound:Sound;
			
			var url:String
			for each (url in playlistURLs)
			{
				if (!url || url == "")
					continue;
				
				request = new URLRequest(url);
				sound = new Sound();
				
				addSoundListeners(sound);
				sound.load(request);
				
				queue.push(sound);
			}
		}
		
		//////////////////////////////////////////////////////////
		//	EVT HANDLERS
		//////////////////////////////////////////////////////////
		
		protected function onIOError_playlistHandler (evt:IOErrorEvent):void
		{
			var sound:Sound = Sound(evt.target);
			removeSoundListeners(sound);
		}
		
		protected function onComplete_playlistHandler (evt:Event):void
		{
			currentSound = Sound(evt.target);
			removeSoundListeners(currentSound);
			
			//since we need to kick off the first song manually, we do so here
			if (isFirstSound)
			{
				currentChannel = currentSound.play();
				currentChannel.addEventListener(Event.SOUND_COMPLETE, onSoundComplete_channelHandler, false, 0.0, false);
				
				isFirstSound = false;
			}
		}
		
		/**
		 * This handler is triggered once the currently playing media has finished playing.
		 * The finshed media then unregistered this handler, registers the next media file in the queue to this handler and then begins playing again (if applicable)
		 * 
		 * @param evt The soundComplete event dispatched from the recently playing media.
		 */
		protected function onSoundComplete_channelHandler (evt:Event):void
		{
			var channel:SoundChannel = SoundChannel(evt.target);
			channel.removeEventListener(Event.SOUND_COMPLETE, onSoundComplete_channelHandler, false);
			
			if (index != -1 && index < queue.length - 1)
			{
				index++
				
				currentSound = Sound(queue[index]);
				
				currentChannel = currentSound.play();
				currentChannel.addEventListener(Event.SOUND_COMPLETE, onSoundComplete_channelHandler, false, 0.0, false);
			}
			
			else
			{
				// we are done so reset the index = -1; indicate visually that its over too.
				index = -1; //this will allow for listBase classes to reset visually as well if they are bound to the index
				if (currentMediaControl)
					currentMediaControl.isPlaying = false;
			}
		}
		
		//////////////////////////////////////////////////////////
		//	CONVENIENCE METHODS
		//////////////////////////////////////////////////////////
		
		protected function addSoundListeners (s:Sound, useWeak:Boolean = false):void
		{			
			s.addEventListener(IOErrorEvent.IO_ERROR, onIOError_playlistHandler, false, 0.0, useWeak);
			s.addEventListener(Event.COMPLETE, onComplete_playlistHandler, false, 0.0, useWeak);
		}
		
		protected function removeSoundListeners (s:Sound):void
		{
			s.removeEventListener(IOErrorEvent.IO_ERROR, onIOError_playlistHandler, false);
			s.removeEventListener(Event.COMPLETE, onComplete_playlistHandler, false);
		}
	}
}